package com.springboot.file.util;

import cn.hutool.core.util.ObjectUtil;
import com.springboot.file.exception.BusinessException;
import com.springboot.file.util.regex.RegexUtil;
import lombok.extern.log4j.Log4j2;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.*;

/**
 * 用于高精确处理常用的数学运算
 * 由于Java的简单类型不能够精确的对浮点数进行运算，这个工具类提供精确的浮点数运算，包括加减乘除和四舍五入及类型转换。
 */
@Log4j2
public class BigDecimalUtil {
    private BigDecimalUtil() {}

    /**
     * 默认精度
     */
    private static final int DEFAULT_SCALE = 2;

    /**
     * 提供精确的加法运算
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */
    public static BigDecimal add(Object v1, Object v2) {
        return add(v1, v2, DEFAULT_SCALE);
    }

    /**
     * 提供精确的加法运算
     *
     * @param v1    被加数
     * @param v2    加数
     * @param scale 保留scale 位小数
     * @return 两个参数的和
     */
    public static BigDecimal add(Object v1, Object v2, int scale) {
        validateScale(scale);
        BigDecimal b1 = toBigDecimal(v1, BigDecimal.ZERO);
        BigDecimal b2 = toBigDecimal(v2, BigDecimal.ZERO);
        return b1.add(b2).setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * 提供精确的减法运算。
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static BigDecimal sub(Object v1, Object v2) {
        return sub(v1, v2, DEFAULT_SCALE);
    }

    /**
     * 提供精确的减法运算
     *
     * @param v1    被减数
     * @param v2    减数
     * @param scale 保留scale 位小数
     * @return 两个参数的差
     */
    public static BigDecimal sub(Object v1, Object v2, int scale) {
        validateScale(scale);
        BigDecimal b1 = toBigDecimal(v1, BigDecimal.ZERO);
        BigDecimal b2 = toBigDecimal(v2, BigDecimal.ZERO);
        return b1.subtract(b2).setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * 提供精确的乘法运算
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static BigDecimal mul(Object v1, Object v2) {
        return mul(v1, v2, DEFAULT_SCALE);
    }

    /**
     * 提供精确的乘法运算
     *
     * @param v1    被乘数
     * @param v2    乘数
     * @param scale 保留scale 位小数
     * @return 两个参数的积
     */
    public static BigDecimal mul(Object v1, Object v2, int scale) {
        validateScale(scale);
        BigDecimal b1 = toBigDecimal(v1, BigDecimal.ZERO);
        BigDecimal b2 = toBigDecimal(v2, BigDecimal.ZERO);
        return b1.multiply(b2).setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * 提供精确的除法运算
     *
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */
    public static BigDecimal div(Object v1, Object v2) {
        return div(v1, v2, DEFAULT_SCALE);
    }

    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指
     * 定精度，以后的数字四舍五入
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示需要精确到小数点以后几位
     * @return 两个参数的商
     */
    public static BigDecimal div(Object v1, Object v2, int scale) {
        validateScale(scale);
        BigDecimal b1 = toBigDecimal(v1, BigDecimal.ZERO);
        BigDecimal b2 = toBigDecimal(v2, BigDecimal.ZERO);
        validateDivisor(b2, scale);
        return b1.divide(b2, scale, RoundingMode.HALF_UP);
    }

    /**
     * 提供精确的小数位四舍五入处理
     *
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static BigDecimal round(Object v, int scale) {
        validateScale(scale);
        BigDecimal b = toBigDecimal(v, BigDecimal.ZERO);
        return b.setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * 比较大小
     *
     * @param v1 被比较数
     * @param v2 比较数
     * @return 如果v1 大于v2 则 返回true 否则false
     */
    public static boolean compareGreaterThan(Object v1, Object v2) {
        BigDecimal b1 = toBigDecimal(v1, BigDecimal.ZERO);
        BigDecimal b2 = toBigDecimal(v2, BigDecimal.ZERO);
        return b1.compareTo(b2) > 0;
    }

    /**
     * 比较大小
     *
     * @param v1 被比较数
     * @param v2 比较数
     * @return 如果v1 大于v2 则 返回true 否则false
     */
    public static int compare(Object v1, Object v2) {
        BigDecimal b1 = toBigDecimal(v1, BigDecimal.ZERO);
        BigDecimal b2 = toBigDecimal(v2, BigDecimal.ZERO);
        return b1.compareTo(b2);
    }

    /**
     * Object转BigDecimal类型
     *
     * @param value 要转换的参数
     * @return BigDecimal 转换后输出的参数
     */
    public static BigDecimal objToBigDecimal(Object value) {
        return toBigDecimal(value, null);
    }

    /**
     * Object转为BigDecimal类型并四舍五入保留N位小数
     *
     * @param obj 要转换的参数
     * @param scale 保留的小数位数
     * @return BigDecimal 转换后输出的参数
     */
    public static BigDecimal objToBigDecimalScale(Object obj, int scale) {
        validateScale(scale);
        BigDecimal bd = BigDecimal.ZERO;
        if (ObjectUtil.isNotEmpty(obj)) {
            bd = toBigDecimal(obj, BigDecimal.ZERO).setScale(scale, RoundingMode.HALF_UP);
        }
        return bd;
    }

    /**
     * BigDecimal转换为String类型并避免输出科学计数法
     *
     * @param bd 要转换的参数
     * @return String 转换后输出的参数
     */
    public static String bigDecimalToStr(BigDecimal bd) {
        if (ObjectUtil.isEmpty(bd)) {
            return null;
        }
        return bd.stripTrailingZeros().toPlainString();
    }

    /**
     * BigDecimal转换为Integer
     *
     * @param bd 要转换的参数
     * @return Integer 转换后输出的参数
     */
    public static Integer bigDecimalToInteger(BigDecimal bd) {
        return ObjectUtil.isNotEmpty(bd) ? bd.intValue() : 0;
    }

    /**
     * 判断BigDecimal类型值是否大于0，是返回true、否返回false
     *
     * @param bd BigDecimal
     * @return boolean
     */
    public static boolean isGreaterThanZero(BigDecimal bd) {
        return ObjectUtil.isNotEmpty(bd) && bd.signum() > 0;
    }

    /**
     * 判断BigDecimal类型值是否小于0，是返回true、否返回false
     *
     * @param bd BigDecimal
     * @return boolean
     */
    public static boolean isLessThanZero(BigDecimal bd) {
        return ObjectUtil.isNotEmpty(bd) && bd.signum() < 0;
    }

    /**
     * 判断BigDecimal类型值是否等于0，是返回true、否返回false
     * @param bd BigDecimal
     * @return boolean
     */
    public static boolean isEqualZero(BigDecimal bd) {
        return ObjectUtil.isNotEmpty(bd) && bd.signum() == 0;
    }

    /**
     * 小数点后N位截取
     *
     * @param bigDecimal 需要截取的BigDecimal
     * @param scale 保留几位小数
     * @return String
     */
    public static String getBigDecimalPoint(String bigDecimal, int scale) {
        if (scale < 0 || !bigDecimal.contains(".")) {
            return bigDecimal;
        }
        if (scale == 0) {
            return bigDecimal.split("\\.")[0];
        }
        BigDecimal b = new BigDecimal(bigDecimal);
        return b.setScale(scale, RoundingMode.HALF_UP).toString();
    }

    /**
     * 将Bigdecial以财务形式显示,千分位表示，保留N位小数
     * @param number 需要转换的数据
     * @param i 保留N位，如保留一位小数，输入1，结果是1,000,000.0这种格式
     * @return java.lang.String
     */
    public static String bigDecimalFormatToFinance(BigDecimal number, Integer i) {
        if (number == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder("#,###");
        if (i > 0) {
            sb.append(".");
            while(--i >= 0) {
                sb.append("0");
            }
        }
        NumberFormat nf = new DecimalFormat(sb.toString());
        String result = nf.format(number);
        return "0".equals(result) ? "" : result;
    }

    /**
     * 逗号','千位符的String转Bigdecimal
     * 如1,000,000 -> 1000000
     * 1,000,000.0 -> 1000000.0
     * 1,000,000.00 -> 1000000.00
     * @param str 需要转换的字符串
     * @return java.math.BigDecimal
     */
    public static BigDecimal financeFormatToBigDecimal(String str) {
        if (ObjectUtil.isEmpty(str)) {
            return null;
        }
        DecimalFormat format = new DecimalFormat();
        format.setParseBigDecimal(true);
        ParsePosition position = new ParsePosition(0);
        return (BigDecimal) format.parse(str, position);
    }

    /**
     * Object 小数转为百分比字符串并保留两位小数
     * @param obj 对象
     * @return java.lang.String
     */
    public static String objToPercent(Object obj) {
        if (ObjectUtil.isEmpty(obj)) {
            return null;
        }
        DecimalFormat df = new DecimalFormat("0.00%");
        BigDecimal d = toBigDecimal(obj, BigDecimal.ZERO);
        return df.format(d);
    }

    /**
     * 百分比字符串数字转BigDecimal
     * 例如：10.01% -> 0.01001
     *      100% -> 1
     * @param str 需要转BigDecimal的百分比字符串
     * @return java.math.BigDecimal BigDecimal结果
     */
    public static BigDecimal percentToBigDecimal(String str) {
        if (ObjectUtil.isEmpty(str)) {
            return null;
        }
        str = str.replace("%", "");
        if (RegexUtil.isNumeric(str)) {
            return div(str, "100");
        }
        return null;
    }

    /**
     * 百分比计算
     * @param obj 金额
     * @param percent 百分比数
     * @return BigDecimal 默认四舍五入
     */
    public static BigDecimal multiplyPercent(Object obj, BigDecimal percent) {
        BigDecimal value = toBigDecimal(obj, BigDecimal.ZERO);
        BigDecimal pct = defaultZero(percent);
        if (isEqualZero(value) || isEqualZero(pct)) {
            return BigDecimal.ZERO;
        }
        return value.multiply(pct).divide(new BigDecimal("100"), DEFAULT_SCALE, RoundingMode.HALF_UP);
    }

    /**
     * 如果num为Null，默认为0
     * @param num 数值
     * @return BigDecimal
     */
    public static BigDecimal defaultZero(BigDecimal num) {
        return num != null ? num : BigDecimal.ZERO;
    }

    /**
     * 求最小数值
     * @param num 数值
     * @return BigDecimal
     */
    public static BigDecimal min(BigDecimal... num) {
        if (ObjectUtil.isEmpty(num)) {
            return null;
        }
        return Arrays.stream(num)
                .filter(Objects::nonNull)
                .min(BigDecimal::compareTo)
                .orElse(null);
    }

    /**
     * 求最大数值
     * @param num 数值
     * @return BigDecimal
     */
    public static BigDecimal max(BigDecimal... num) {
        if (ObjectUtil.isEmpty(num)) {
            return null;
        }
        return Arrays.stream(num)
                .filter(Objects::nonNull)
                .max(BigDecimal::compareTo)
                .orElse(null);
    }

    /**
     * 转成str
     * @param obj 对象
     * @return String
     */
    public static String objToStr(Object obj) {
        if (ObjectUtil.isEmpty(obj)) {
            throw new IllegalArgumentException("对象不能为空");
        }
        if (!RegexUtil.isNumeric(obj.toString())) {
            throw new IllegalArgumentException("[" + obj + "]不是一个数字对象");
        }
        return bigDecimalToStr(toBigDecimal(obj, BigDecimal.ZERO));
    }

    /**
     * 将Object中的BigDecimal字段乘以给定的数
     * 计算出的结果如果还是无限接近于0的数（小数点超过6位小数，如 0.0000001），会以科学计数法的形式输出，如有必要，请自行转换为String
     * @param object 需要操作的对象
     * @param multiplier 乘数
     * @param excludedFields 不需要乘以的BigDecimal字段名称
     */
    public static void multiplyBigDecimalsForObject(Object object, BigDecimal multiplier, String... excludedFields) {
        if (object == null) {
            throw new IllegalArgumentException("Object must not be null.");
        }
        if (multiplier == null) {
            throw new IllegalArgumentException("Multiplier must not be null.");
        }

        Set<String> excludedFieldSet = new HashSet<>(Arrays.asList(excludedFields));

        if (object instanceof Map) {
            multiplyBigDecimalsForMap((Map<String, Object>) object, multiplier, excludedFieldSet);
        } else {
            multiplyBigDecimalsForFields(object, multiplier, excludedFieldSet);
        }
    }

    /**
     * 将List中的BigDecimal字段乘以给定的数
     * 计算出的结果如果还是无限接近于0的数（小数点超过6位小数，如 0.0000001），会以科学计数法的形式输出，如有必要，请自行转换为String
     * @param list          需要操作的List对象
     * @param multiplier    乘数
     * @param excludeFields 不需要乘以的BigDecimal字段名称
     */
    public static void multiplyBigDecimalFieldsForList(List<Object> list, BigDecimal multiplier, String... excludeFields) {
        if (list == null || multiplier == null) {
            return;
        }
        Set<String> excludeFieldsSet = new HashSet<>(Arrays.asList(excludeFields));
        for (Object obj : list) {
            if (obj != null) {
                multiplyBigDecimalsForFields(obj, multiplier, excludeFieldsSet);
            }
        }
    }

    /**
     * 将对象中的BigDecimal字段除以给定的数
     * 计算出的结果如果还是无限接近于0的数（小数点超过6位小数，如 0.0000001），会以科学计数法的形式输出，如有必要，请自行转换为String
     * @param object        需要操作的对象
     * @param divisor       除数
     * @param excludedFields 不需要除以的BigDecimal字段名称
     */
    public static void divideBigDecimalsForObject(Object object, BigDecimal divisor, String... excludedFields) {
        if (object == null) {
            throw new IllegalArgumentException("Object must not be null.");
        }
        if (divisor == null || isEqualZero(divisor)) {
            throw new IllegalArgumentException("Divisor must not be null and must not be zero.");
        }

        Set<String> excludedFieldSet = new HashSet<>(Arrays.asList(excludedFields));

        if (object instanceof Map) {
            divideBigDecimalsForMap((Map<String, Object>) object, divisor, excludedFieldSet);
        } else {
            divideBigDecimalsForFields(object, divisor, excludedFieldSet);
        }
    }

    /**
     * 将List中的BigDecimal字段除以给定的数
     * 计算出的结果如果还是无限接近于0的数（小数点超过6位小数，如 0.0000001），会以科学计数法的形式输出，如有必要，请自行转换为String
     * @param list          需要操作的List对象
     * @param divisor       除数
     * @param excludeFields 不需要除以的BigDecimal字段名称
     */
    public static void divideBigDecimalFieldsForList(List<Object> list, BigDecimal divisor, String... excludeFields) {
        if (list == null || divisor == null || isEqualZero(divisor)) {
            return;
        }
        Set<String> excludeFieldsSet = new HashSet<>(Arrays.asList(excludeFields));
        for (Object obj : list) {
            if (obj != null) {
                divideBigDecimalsForFields(obj, divisor, excludeFieldsSet);
            }
        }
    }

    // 私有辅助方法

    private static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) {
        if (value == null) {
            return defaultValue;
        }
        try {
            if (value instanceof BigDecimal) {
                return (BigDecimal) value;
            }
            if (value instanceof String) {
                String str = ((String) value).trim();
                if (str.isEmpty()) {
                    return defaultValue;
                }
                return new BigDecimal(str);
            }
            if (value instanceof Number) {
                return new BigDecimal(value.toString());
            }
            if (value instanceof BigInteger) {
                return new BigDecimal((BigInteger) value);
            }
            throw new ClassCastException("Not possible to coerce [" + value + "] from class " + value.getClass()
                    + " into a BigDecimal.");
        } catch (Exception e) {
            log.error("转换BigDecimal失败: {}", e.getMessage());
            return defaultValue;
        }
    }

    private static void validateScale(int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
    }

    private static void validateDivisor(BigDecimal divisor, int scale) {
        if (isEqualZero(divisor)) {
            throw new IllegalArgumentException("Divider cannot be equal to zero");
        }
        if (divisor.abs().compareTo(new BigDecimal("1e-" + scale)) < 0) {
            throw new IllegalArgumentException("Divider is too close to zero");
        }
    }

    private static void multiplyBigDecimalsForMap(Map<String, Object> map, BigDecimal multiplier, Set<String> excludedFields) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() instanceof BigDecimal && !excludedFields.contains(entry.getKey())) {
                BigDecimal value = (BigDecimal) entry.getValue();
                entry.setValue(value.multiply(multiplier));
            }
        }
    }

    private static void multiplyBigDecimalsForFields(Object object, BigDecimal multiplier, Set<String> excludedFields) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            
            for (PropertyDescriptor property : propertyDescriptors) {
                if (BigDecimal.class.equals(property.getPropertyType()) && 
                    !excludedFields.contains(property.getName())) {
                    Method readMethod = property.getReadMethod();
                    Method writeMethod = property.getWriteMethod();
                    
                    if (readMethod != null && writeMethod != null) {
                        BigDecimal value = (BigDecimal) readMethod.invoke(object);
                        if (value != null) {
                            BigDecimal newValue = value.multiply(multiplier);
                            writeMethod.invoke(object, newValue);
                        }
                    }
                }
            }
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            log.error("Failed to process BigDecimal fields", e);
            throw new BusinessException("Failed to process BigDecimal fields");
        }
    }

    private static void divideBigDecimalsForMap(Map<String, Object> map, BigDecimal divisor, Set<String> excludedFields) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() instanceof BigDecimal && !excludedFields.contains(entry.getKey())) {
                BigDecimal value = (BigDecimal) entry.getValue();
                entry.setValue(value.divide(divisor, DEFAULT_SCALE, RoundingMode.HALF_UP));
            }
        }
    }

    private static void divideBigDecimalsForFields(Object object, BigDecimal divisor, Set<String> excludedFields) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            
            for (PropertyDescriptor property : propertyDescriptors) {
                if (BigDecimal.class.equals(property.getPropertyType()) && 
                    !excludedFields.contains(property.getName())) {
                    Method readMethod = property.getReadMethod();
                    Method writeMethod = property.getWriteMethod();
                    
                    if (readMethod != null && writeMethod != null) {
                        BigDecimal value = (BigDecimal) readMethod.invoke(object);
                        if (value != null) {
                            BigDecimal newValue = value.divide(divisor, DEFAULT_SCALE, RoundingMode.HALF_UP);
                            writeMethod.invoke(object, newValue);
                        }
                    }
                }
            }
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            log.error("Failed to process BigDecimal fields", e);
            throw new BusinessException("Failed to process BigDecimal fields");
        }
    }
}
